package com.hwtx.form.domain.def.meta;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.Lists;
import com.hwtx.form.domain.def.FormConfigExt;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.def.FormProperties;
import com.hwtx.form.domain.def.GenFormContextHolder;
import com.hwtx.form.domain.def.meta.config.FormComponentConfig;
import com.hwtx.form.domain.def.meta.config.GroupChainSelectFormComponentConfig;
import com.hwtx.form.domain.def.meta.config.SelectComponentConfig;
import com.hwtx.form.domain.vo.FormConfigQueryVo;
import com.hwtx.form.persistence.entity.FormDefDetail;
import com.hwtx.form.util.FormUtil;
import com.hwtx.form.util.JacksonUtil;
import io.geekidea.boot.auth.cache.LoginCache;
import io.geekidea.boot.framework.exception.BusinessException;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.MessageSource;

import java.util.*;
import java.util.stream.Collectors;

import static com.hwtx.form.domain.FormConstants.*;
import static com.hwtx.form.domain.FormConstants.FormComponentType.FORM_COM_SINGLE_TEXT;
import static com.hwtx.form.domain.def.meta.Button.buildListExtComponents;
import static com.hwtx.form.domain.def.meta.Component.Editor.getEditorComponent;
import static com.hwtx.form.domain.def.meta.Component.Input.getInputComponent;
import static io.geekidea.boot.config.MessageSourceConfig.I18N_MESSAGE_SOURCE;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Component {
    public static final String INPUT_TEXT = "input-text";
    public static final String INPUT_NUMBER = "input-number";
    public static final String INPUT_PASSWORD = "input-password";
    public static final String INPUT_DATETIME = "input-datetime";
    public static final String INPUT_PHONE = "phone";
    public static final String INPUT_EMAIL = "email";

    private FormProperties.Meta meta;
    private List<Component> body;
    private String id;
    private String name;
    private String authId;
    private Object label;
    private Style style;
    private String placeholder;
    private String type;
    private Boolean clearable;
    private OnEvent onEvent;
    private String disabledOn;
    private String visibleOn;

    public Component(String type) {
        this.type = type;
    }


    Component() {
    }

    public static String getComponentId() {
        FormDef formDef = GenFormContextHolder.getFormDef();
        if (formDef == null) {
            throw new BusinessException("未获得到表单定义信息");
        }
        return FormUtil.getFormAuthCode(formDef, null);
    }

    public static List<Component> buildCanWithoutController(List<FormConfigQueryVo> formConfigQueryList, String type) {
        return _buildControllers(formConfigQueryList, type, true);
    }

    public static List<Component> buildControllers(List<FormConfigQueryVo> formConfigQueryList, String type) {
        return _buildControllers(formConfigQueryList, type, false);
    }

    public static List<Component> buildControllers(FormConfigQueryVo formConfigQuery, FormComponentType formComponentType,
                                                   String type) {
        List<Component> components = Lists.newArrayList();
        switch (formComponentType) {
            case FORM_COM_HIDDEN:
                Hidden hidden = new Hidden();
                hidden.setName(StringUtils.isNotBlank(formConfigQuery.getName()) ? formConfigQuery.getName() : formConfigQuery.getField());
                components.add(hidden);
                break;
            case FORM_COM_SINGLE_TEXT:
                components.add(getInputComponent(Component.INPUT_TEXT, formConfigQuery, type));
                break;
            case FORM_COM_DIGITAL_INPUT:
                components.add(getInputComponent(Component.INPUT_NUMBER, formConfigQuery, type));
                break;
            case FORM_COM_PWD:
                components.add(getInputComponent(Component.INPUT_PASSWORD, formConfigQuery, type));
                break;
            case FORM_COM_PHONE:
                components.add(getInputComponent(Component.INPUT_PHONE, formConfigQuery, type));
                break;
            case FORM_COM_EMAIL:
                components.add(getInputComponent(Component.INPUT_EMAIL, formConfigQuery, type));
                break;
            case FORM_COM_CODE_EDITOR:
                components.add(getEditorComponent(formConfigQuery, "java", type));
                break;
            case FORM_COM_IMAGE:
                components.add(getImageComponent(formConfigQuery));
                break;
            case FORM_COM_FILE:
                break;
            case FORM_COM_CHAINED_SELECT:
                components.add(getChainedSelect(formConfigQuery, type));
                break;
            case FORM_COM_2_GROUP_CHAINED_SELECT:
                components.addAll(getGroupChainedSelect(formConfigQuery, type, formComponentType.getValue(), 2));
                break;
            case FORM_COM_3_GROUP_CHAINED_SELECT:
                components.addAll(getGroupChainedSelect(formConfigQuery, type, formComponentType.getValue(), 3));
                break;
            case FORM_COM_4_GROUP_CHAINED_SELECT:
                components.addAll(getGroupChainedSelect(formConfigQuery, type, formComponentType.getValue(), 4));
                break;
            case FORM_COM_TEXT:
                Html html = new Html();
                html.setHtml(formConfigQuery.getDescription() + "<br/><br/>");
//                html.setFormBatProp();
                components.add(html);
                break;
            case FORM_COM_SINGLE_SELECT:
            case FORM_COM_MUTIL_SELECT:
                Select select = new Select();
                select.setName(formConfigQuery.getField());
                select.setLabel(formConfigQuery.getDescription());
                select.setClearable(true);
                FormConfigExt formConfigExt = FormUtil.getFormConfigExt(formConfigQuery.getExt());
                if (formConfigExt != null) {
                    FormConfigExt.FormConfigCtr formConfigCtr = formConfigExt.getQuery() == null ? null : formConfigExt.getQuery().getConfigCtr();
                    if (Objects.equals(type, FORM)) {
                        if (formConfigExt.getForm() == null) {
                            throw new BusinessException("未找到表单配置信息，formId = " + formConfigQuery.getFormId() + "，字段=" + formConfigQuery.getField() + "，组件类型=" + formComponentType.getValue());
                        }
                        formConfigCtr = formConfigExt.getForm().getConfigCtr();
                    }
                    if (formConfigCtr == null) {
                        throw new BusinessException("未找到表单配置信息，formId = " + formConfigQuery.getFormId() + "，字段=" + formConfigQuery.getField() + "，组件类型=" + formComponentType.getValue());
                    }
                    formConfigCtr.setCtrType(formComponentType.getValue());
                    SelectComponentConfig.buildSelectComponent(select, formConfigQuery, formConfigCtr);
                }
                if (Objects.equals(type, FORM)) {
                    select.setFormBatProp();
                    select.setRequired(Objects.equals(formConfigQuery.getIsRequired(), (byte) 1));
                } else {
                    select.setSize("sm");
                }
                components.add(select);
            case null:
            case FORM_COM_MULTI_TEXT:
            case FORM_COM_RICH_TEXT:
            case FORM_COM_DATE:
            case FORM_COM_DATE_TIME:
            case FORM_COM_API_INVOKE:
            case FORM_COM_RADIO:
            case FORM_COM_CHECK_BOX:
            case LINE_CHART:
            case PIE_CHART:
        }
        return components;
    }

    private static List<Component> _buildControllers(List<FormConfigQueryVo> formConfigQueryList, String type, boolean canNoController) {
        List<Component> components = Lists.newArrayList();
        Map<String, List<FormConfigQueryVo>> groupItems = formConfigQueryList.stream().collect(Collectors.groupingBy(vo -> StringUtils.isNotBlank(vo.getName()) ? vo.getName() : vo.getField()));
        List<String> keys = groupItems.entrySet().stream()
                .filter(entry -> entry.getValue().size() > 1)
                .map(Map.Entry::getKey)
                .toList();
        if (CollectionUtils.isNotEmpty(keys)) {
            if (formConfigQueryList.stream().map(FormConfigQueryVo::getTableName).distinct().count() < 2 ||
                    !keys.stream().allMatch(key -> key.equals(DefaultColumn.id.name()))) {
                throw new BusinessException("存在重复的表单字段，重复字段 = " + keys);
            }
        }
        formConfigQueryList.forEach(formConfigQuery -> {
            int controllerId = getControllerId(type, canNoController, formConfigQuery);
            components.addAll(buildControllers(formConfigQuery, FormComponentType.of(controllerId), type));
        });
        return components;
    }

    private static List<Component> getGroupChainedSelect(FormConfigQueryVo formConfigQuery, String type, Integer controllerId, int slot) {
        FormComponentType formComponentType = FormComponentType.of(controllerId);
        if (formComponentType == null) {
            throw new BusinessException("组件不存在，id=" + controllerId);
        }
        FormConfigExt formConfigExt = FormUtil.getFormConfigExt(formConfigQuery.getExt());
        if (formConfigExt == null) {
            throw new BusinessException("未找到表单扩展信息，formId = " + formConfigQuery.getFormId() + ",字段=" + formConfigQuery.getField());
        }
        FormConfigExt.FormConfigCtr formConfigCtr = FormUtil.getFormConfigCtr(type, formConfigExt);
        if (formConfigCtr == null) {
            throw new BusinessException("未找到组件配置信息，formId = " + formConfigQuery.getFormId() + ",字段=" + formConfigQuery.getField() + ",组件类型=" + formComponentType.getDesc());
        }
        if (formConfigCtr.getComExt() == null) {
            throw new BusinessException("未找到组件扩展信息，formId = " + formConfigQuery.getFormId() + ",字段=" + formConfigQuery.getField() + ",组件类型=" + formComponentType.getDesc());
        }
        List<FormComponentConfig.ParamMapping> outputMapping = GroupChainSelectFormComponentConfig.getReadOutput(formConfigCtr.getComExt());
        if (CollectionUtils.isEmpty(outputMapping)) {
            throw new BusinessException("级联读服务无返回字段，formId = " + formConfigQuery.getFormId() + ",字段=" + formConfigQuery.getField() + ",组件类型=" + formComponentType.getDesc());
        }
        if (outputMapping.size() != slot) {
            throw new BusinessException("级联读服务返回字段数量与配置不一致，formId = " + formConfigQuery.getFormId() + ",字段=" + formConfigQuery.getField() + ",组件类型=" + formComponentType.getDesc());
        }
        String parentId = "";
        List<Component> items = new ArrayList<>();
        items.add(Hidden.buildHidden(GroupChainSelectFormComponentConfig.getGroupChainSelectId(), null));
        for (int i = 0; i < slot; i++) {
            Select select = new Select();
            FormComponentConfig.ParamMapping output = outputMapping.get(i);
            select.setName(StringUtils.isNotBlank(output.getAlias()) ? output.getAlias() : output.getKey());
            select.setPlaceholder(output.getRemark());
            select.setClearable(true);
            select.setSearchable(true);
            select.setSize("sm");
            select.setRequired(Objects.equals(formConfigQuery.getIsRequired(), (byte) 1));
            select.setSource(ChainedSelect.getSource(formConfigCtr.getComExt(), List.of(parentId)));
            parentId = "${" + select.getName() + "}";
            items.add(select);
        }
        FormBase component = ChainedSelect.GroupChainedSelect.build(formConfigQuery.getName(),
                formConfigQuery.getDescription(), true, Objects.equals(formConfigQuery.getIsRequired(), (byte) 1), items);
        component.setFormBatProp();

        return List.of(component, Hidden.buildHidden(GroupChainSelectFormComponentConfig.getGroupChainIds(), null));
    }

    private static Component getChainedSelect(FormConfigQueryVo formConfigQuery, String type) {
        Api api = null;
        FormConfigExt formConfigExt = FormUtil.getFormConfigExt(formConfigQuery.getExt());
        if (formConfigExt != null) {
            FormConfigExt.FormConfigCtr configCtr = null;
            if (Objects.equals(QUERY, type)) {
                if (formConfigExt.getQuery() == null) {
                    throw new BusinessException("未找到查询配置信息，formId = " + formConfigQuery.getFormId() + ",字段=" + formConfigQuery.getField());
                }
                configCtr = formConfigExt.getQuery().getConfigCtr();
            } else if (Objects.equals(FORM, type)) {
                if (formConfigExt.getForm() == null) {
                    throw new BusinessException("未找到表单配置信息，formId = " + formConfigQuery.getFormId() + ",字段=" + formConfigQuery.getField());
                }
                configCtr = formConfigExt.getForm().getConfigCtr();
            }
            if (configCtr != null) {
                api = ChainedSelect.getSource(configCtr.getComExt(), List.of("${parentId}"));
            }
        }
        return getChainedSelect(formConfigQuery, type, api);
    }

    private static @NotNull ChainedSelect getChainedSelect(FormConfigQueryVo formConfigQuery, String type, Api api) {
        ChainedSelect chainedSelect = new ChainedSelect();
        chainedSelect.setSource(api);
        chainedSelect.setClearable(true);
        chainedSelect.setLabel(formConfigQuery.getDescription());
        chainedSelect.setSearchable(true);
        if (Objects.equals(type, FORM)) {
            chainedSelect.setFormBatProp();
        }
        chainedSelect.setName(formConfigQuery.getName());
        if (Objects.equals(type, FORM)) {
            chainedSelect.setRequired(Objects.equals(formConfigQuery.getIsRequired(), (byte) 1));
        }
        return chainedSelect;
    }

    private static Component getImageComponent(FormConfigQueryVo formConfigQuery) {
        InputImage image = new InputImage();
        image.setName(formConfigQuery.getField());
        image.setEnlargeAble(true);
        image.setLabel(formConfigQuery.getDescription());
        image.setReceiver(GenFormContextHolder.getDomain() + GenFormContextHolder.getContextPath() + "/common/upload");
        FormConfigExt formConfigExt = FormUtil.getFormConfigExt(formConfigQuery.getExt());
        if (formConfigExt != null && formConfigExt.getForm() != null) {
            FormConfigExt.FormConfig formConfig = formConfigExt.getForm();
            if (formConfig.getLabelWidth() != null) {
                image.setLabelWidth(formConfig.getLabelWidth() + "px");
            }
            if (formConfig.getLabelAlign() != null) {
                image.setLabelAlign(formConfig.getLabelAlign());
                image.setHorizontalMode();
            }
        } else {
            image.setFormBatProp();
        }
        image.setRequired(Objects.equals(formConfigQuery.getIsRequired(), (byte) 1));
        return image;
    }

    private static @NotNull Integer getControllerId(String type, boolean canNoController, FormConfigQueryVo formConfigQuery) {
        boolean isQuery = Objects.equals(type, QUERY);
        Integer controllerId = isQuery ? formConfigQuery.getQueryController() : formConfigQuery.getFormController();
        if (controllerId != null && controllerId == 0) {
            controllerId = null;
        }
        if (controllerId == null) {
            if (!canNoController) {
                throw new BusinessException("构建" + (isQuery ? "查询配置" : "表单配置") + "时，未找到对应的组件类型，组件 = " + formConfigQuery.getField() + ",formId = " + formConfigQuery.getFormId());
            } else {
                controllerId = FORM_COM_SINGLE_TEXT.getValue();
            }
        }
        return controllerId;
    }

    public static List<FormConfigQueryVo> getValidateFormItemsWithoutHidden(List<FormConfigQueryVo> formConfigQueryList, FormDefDetail formDefDetail) {
        formConfigQueryList = formConfigQueryList.stream().filter(formConfigQuery -> Objects.equals(formConfigQuery.getIsFormShow(), (byte) 1))
                .sorted(Comparator.comparing(FormConfigQueryVo::getNum)).collect(Collectors.toList());
        if (formConfigQueryList.isEmpty()) {
            throw new RuntimeException("不存在可展示的表单项，formId = " + formDefDetail.getFormId());
        }
        return formConfigQueryList;
    }

    public static List<FormConfigQueryVo> getValidateFormItems(List<FormConfigQueryVo> formConfigQueryList) {
        return formConfigQueryList.stream().filter(formConfigQuery -> {
            boolean show = Objects.equals(formConfigQuery.getIsFormShow(), (byte) 1);
            if (!show && formConfigQuery.getFormController() != null) {
                return formConfigQuery.getFormController() == FormComponentType.FORM_COM_HIDDEN.getValue();
            }
            return show;
        }).sorted(Comparator.comparing(FormConfigQueryVo::getNum)).collect(Collectors.toList());
    }

    @Data
    public static class TmpFromStruct {
        private String type;
        private FormProperties.Meta meta;
    }


    @EqualsAndHashCode(callSuper = true)
    public static class Page extends Component {
        public Page() {
            super("page");
        }

        public static String getTypeName() {
            return "page";
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class JsonContent extends Component {
        private String content;

        public JsonContent() {
            super(null);
        }

        public static String getTypeName() {
            return "jsonContent";
        }

        @JsonIgnore
        public String getContent() {
            return content;
        }

        @JsonAnyGetter
        public Map<String, Object> getContentMap() {
            if (content == null) return Collections.emptyMap();
            return JacksonUtil.json2Map(content);
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Html extends FormBase {
        private String html;

        public Html() {
            super("html");
        }

        public static String getTypeName() {
            return "html";
        }
    }

    @EqualsAndHashCode
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Validations {

        private Integer maxLength;
        private Integer minLength;
        private Boolean isEmail;
        private Boolean isPhoneNumber;
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Dialog extends Component {
        private String title;
        private String size;
        private Boolean closeOnEsc = true;
        private Boolean closeOnOutside = true;
        private String bodyClassName;
        private Map<String, Object> data;
        private List<Action> actions;

        public static String getTypeName() {
            return "dialog";
        }
    }


    @EqualsAndHashCode(callSuper = true)
    public static class Group extends Component {
        public Group() {
            super("group");
        }

        public static void groupComponents(FormLayout layout, Component parent, List<Component> components) {
            MessageSource messageSource = SpringUtil.getBean(I18N_MESSAGE_SOURCE);
            if (layout == null) {
                throw new BusinessException(messageSource.getMessage("form.layout.not.null", new Object[]{}, GenFormContextHolder.getLocale()));
            }
            if (layout == FormLayout.LAYOUT_1) {
                parent.setBody(components);
            } else if (layout == FormLayout.LAYOUT_2 || layout == FormLayout.LAYOUT_3) {
                int columns = layout.getCode();
                Map<Boolean, List<Component>> groupByComponents = components.stream().collect(Collectors.groupingBy(component -> Objects.equals(Hidden.TYPE, component.getType())));
                List<Component> normalComponents = groupByComponents.get(false);
                if (normalComponents.isEmpty()) {
                    throw new RuntimeException("分组组件不能为空");
                }
                int lines = (int) Math.ceil((double) normalComponents.size() / columns);
                List<Component> groups = Lists.newArrayList();
                for (int i = 0; i < lines; i++) {
                    List<Component> lineComponents = normalComponents.stream().skip((long) i * columns).limit(columns).collect(Collectors.toList());
                    Group group = new Group();
                    group.setBody(lineComponents);
                    groups.add(group);
                }
                if (CollectionUtils.isNotEmpty(groupByComponents.get(true))) {
                    groups.addAll(groupByComponents.get(true));
                }
                parent.setBody(groups);
            }
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Image extends Component {
        public static final String TYPE = "image";
        public static final String EnlargeAble = "enlargeAble";
        public static final String InnerClassName = "innerClassName";
        public static final String No_Border = "no-border";
        private Boolean enlargeAble;

        public Image() {
            super(TYPE);
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Data
    public static class Combo extends FormBase {
        public Boolean multiple;
        private List<? extends Component> items;

        public Combo() {
            super("combo");
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class InputImage extends FormBase {
        public static final String TYPE = "input-image";
        public static final String EnlargeAble = "enlargeAble";
        public static final String InnerClassName = "innerClassName";
        public static final String No_Border = "no-border";
        private Boolean enlargeAble;
        private String receiver;

        public InputImage() {
            super(TYPE);
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Data
    public static class OnEvent {
        public static final String onEvent = "onEvent";
        private ClickEvent click;
        private ChangeEvent change;
        private SearchEvent search;
        private Map<String, Map<String, List<Action>>> actionsMap;

        @JsonAnyGetter
        public Map<String, Map<String, List<Action>>> getActionsMap() {
            return actionsMap;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Data
    public static class ClickEvent {
        private List<Action> actions;
    }

    @EqualsAndHashCode(callSuper = true)
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Data
    public static class ChangeEvent extends ClickEvent {
    }

    @EqualsAndHashCode(callSuper = true)
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Data
    public static class SearchEvent extends ClickEvent {
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Data
    public static class Action {

        public static final String CLEAR = "clear";
        public static final String SHOW = "show";
        public static final String HIDDEN = "hidden";

        private Boolean silent;
        private String actionType;
        private Api api;
        private String outputVar;
        private String componentId;
        private Args args;
        private Dialog dialog;
        private String expression;
        private Map<String, Object> data;

        public static Action getAjaxAction(Api api, String outputVar) {
            return getAjaxAction(api, outputVar, null);
        }

        public static Action getAjaxAction(Api api, String outputVar, Boolean silent) {
            Action action = new Action();
            action.setActionType("ajax");
            action.setApi(api);
            if (StringUtils.isNotBlank(outputVar)) {
                action.setOutputVar(outputVar);
            }
            if (silent != null && silent) {
                action.setSilent(true);
            }
            return action;
        }

        public static Action getSetValueAction(String value, String componentId) {
            Action action = new Action();
            action.setActionType("setValue");
            action.setComponentId(componentId);
            Args args = new Args();
            args.setValue(value);
            action.setArgs(args);
            return action;
        }

        public static Action getReloadAction(String componentId, Map<String, Object> data) {
            Action action = new Action();
            action.setActionType("reload");
            action.setComponentId(componentId);
            if (MapUtils.isNotEmpty(data)) {
                action.setData(data);
            }
            return action;
        }


        public static Action getBoolAction(String type, String componentId, String expression) {
            return getBoolAction(type, componentId, expression, null);
        }

        public static Action getBoolAction(String type, String componentId, String expression, String outVar) {
            Action action = new Action();
            action.setActionType(type);
            if (StringUtils.isNotBlank(componentId)) {
                action.setComponentId(componentId);
            }
            if (StringUtils.isNotBlank(expression)) {
                action.setExpression(expression);
            }
            if (StringUtils.isNotBlank(outVar)) {
                action.setOutputVar(outVar);
            }
            return action;
        }

        public static Action getStopPropagationAction(String expression) {
            return getBoolAction("stopPropagation", "", expression);
        }

        public static Action getValidateAction(String componentId, String outVar) {
            return getBoolAction("validate", componentId, "", outVar);
        }

        public static Action getBroadcastAction(String eventName) {
            Action action = new Action();
            action.setActionType("broadcast");
            Args args = new Args();
            args.setEventName(eventName);
            action.setArgs(args);
            return action;
        }
    }

    @JsonInclude(JsonInclude.Include.NON_NULL)
    @Data
    public static class Args {
        private String value;
        private String eventName;
    }


    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class FormBase extends Component {
        private String size;
        private String mode;
        private String labelWidth;
        private String labelAlign;
        private Boolean required;
        private Object value;
        private Boolean disabled;
        private Api initApi;
        private Api api;
        private Integer columnRatio;
        private Validations validations;

        FormBase(String type) {
            super(type);
        }

        FormBase() {
        }

        public void setFormBatProp() {
            setMode("horizontal");
            setLabelWidth("var(--sizes-base-30)");
        }

        public void setHorizontalMode() {
            setMode("horizontal");
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Panel extends Component {
        private String title;

    }
    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Form extends Component {
        boolean wrapWithPanel = false;
        private Api initApi;
        private Api api;
        private String title;
        @JsonProperty("static")
        private Boolean formStatic;
        private String submitText;
        private Boolean canAccessSuperData;

        public Form() {
            super("form");
        }

        public static Form buildForm(Long formId, FormDefDetail formDefDetail, boolean init,
                                     List<FormConfigQueryVo> formConfigQueryList, String action) {
            return buildForm(formId, null, formDefDetail, formConfigQueryList, init, false, null, null, LoginCache.get().getUsername(), null, action);
        }

        public static Form buildForm(Long formId, FormProperties.Meta meta, FormDefDetail formDefDetail,
                                     List<FormConfigQueryVo> formConfigQueryList, String genBy, String appendTo, Map<String, Object> params) {
            return buildForm(formId, meta, formDefDetail, formConfigQueryList, true, true, genBy, appendTo, LoginCache.get().getUsername(), params, null);
        }

        private static Form buildForm(Long formId, FormProperties.Meta meta, FormDefDetail formDefDetail,
                                      List<FormConfigQueryVo> formConfigQueryList, boolean init, boolean buildMeta,
                                      String genBy, String appendTo, String createBy, Map<String, Object> params, String action) {
            Form form = new Form();
            if (buildMeta) {
                if (meta == null) {
                    String title = formDefDetail.getKeyId().replaceAll("-", ".") + ".form";
                    meta = new FormProperties.Meta();
                    meta.title("#i18n{" + title + "}");
                    meta.type(FormProperties.MetaType.FORM);
                    meta.genBy(genBy);
                    meta.appendTo(appendTo);
                    meta.createBy(createBy);
                }
                if (StringUtils.isNotBlank(meta.getTitle())) {
                    form.setMeta(meta);
                }
            }
            String domain = GenFormContextHolder.getDomain();
            String contextPath = GenFormContextHolder.getContextPath();
            if (init) {
                StringBuilder param = new StringBuilder();
                StringBuilder sendOn = new StringBuilder();
                sendOn.append("this.id > 0");

                if (MapUtils.isNotEmpty(params)) {
                    params.forEach((key, value) -> {
                        param.append(key).append("=").append(value).append("&");
                        if (value != null) {
                            sendOn.append(" && ").append("this.").append(FormUtil.getParamName(value.toString()));
                        }
                    });
                    param.setLength(param.length() - 1);
                }
                String url = domain + contextPath + "/form/get?valueIds=${id}&formId=" + formId;
                if (!param.isEmpty()) {
                    url += "&" + param;
                }
                form.setInitApi(Api.builder().url(url).method(HTTP_GET).sendOn(sendOn.toString()).build());
            }
            String api = domain + contextPath + "/form/addOrUpdate";
            if (StringUtils.isNotBlank(action)) {
                api = FormUtil.getChainApi(domain, contextPath, action).toString();
            }
            form.setApi(Api.builder().url(api).method(HTTP_POST).build());
            List<Component> components = buildControllers(formConfigQueryList, FORM);
            Group.groupComponents(FormLayout.of(formDefDetail.getLayout()), form, components);
            form.getBody().addFirst(Hidden.buildHidden(FORM_ID, formId));
            return form;
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class TextArea extends FormBase {
        public TextArea() {
            super("textarea");
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Input extends FormBase {
        private String format;

        public static Input getNumber(Object label, String name, String placeholder) {
            Input input = new Input();
            input.setType(INPUT_NUMBER);
            input.setName(name);
            input.setLabel(label);
            input.setPlaceholder(placeholder);
            return input;
        }

        public static Input getDataTime(Object label, String name, String placeholder) {
            Input input = new Input();
            input.setType(INPUT_DATETIME);
            input.setName(name);
            input.setLabel(label);
            input.setPlaceholder(placeholder);
            input.setFormat("YYYY-MM-DD HH:mm:ss");
            return input;
        }

        public static Input getText(Object label, String name, String placeholder) {
            Input input = new Input();
            input.setType(INPUT_TEXT);
            input.setName(name);
            input.setLabel(label);
            input.setPlaceholder(placeholder);
            return input;
        }

        public static Input getInputComponent(String componentType, FormConfigQueryVo formConfigQuery, String configType) {
            Input inputComponent = new Input();
            inputComponent.setName(StringUtils.isNotBlank(formConfigQuery.getName()) ? formConfigQuery.getName() : formConfigQuery.getField());
            if (Objects.equals(componentType, Input.INPUT_EMAIL) || Objects.equals(componentType, Input.INPUT_PHONE)) {
                inputComponent.setType(Component.INPUT_TEXT);
                Validations validations = new Validations();
                validations.setMinLength(5);
                if (Objects.equals(componentType, Input.INPUT_EMAIL)) {
                    validations.setIsEmail(true);
                }
                if (Objects.equals(componentType, Input.INPUT_PHONE)) {
                    validations.setIsPhoneNumber(true);
                }
                inputComponent.setValidations(validations);
            } else {
                inputComponent.setType(componentType);
            }
            if (StringUtils.isNotEmpty(formConfigQuery.getDescription())) {
                inputComponent.setLabel(formConfigQuery.getDescription());
            } else {
                inputComponent.setLabel(false);
            }
            inputComponent.setClearable(true);
            if (Objects.equals(configType, FORM)) {
                if (Objects.equals(formConfigQuery.getIsFormEditable(), (byte) 0)) {
                    inputComponent.setDisabledOn("this.id > 0");
                }
                FormConfigExt.FormConfig formConfig = FormUtil.getFormConfig(formConfigQuery.getExt());
                if (formConfig != null && !formConfig.localEmpty()) {
                    if (formConfig.getLabelWidth() != null) {
                        inputComponent.setLabelWidth(formConfig.getLabelWidth() + "px");
                    }
                    if (formConfig.getLabelAlign() != null) {
                        inputComponent.setLabelAlign(formConfig.getLabelAlign());
                        inputComponent.setHorizontalMode();
                    }
                } else {
                    inputComponent.setFormBatProp();
                }
                inputComponent.setRequired(Objects.equals(formConfigQuery.getIsRequired(), (byte) 1));
            } else {
                inputComponent.setSize("sm");
            }
            return inputComponent;
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public static class Editor extends FormBase {
        String language;

        public Editor() {
            super("editor");
        }

        public static Editor getEditorComponent(FormConfigQueryVo formConfigQuery, String language, String configType) {
            Editor editor = new Editor();
            editor.setName(formConfigQuery.getField());
            editor.setLanguage(language);
            editor.setType("editor");
            if (StringUtils.isNotBlank(formConfigQuery.getDescription())) {
                editor.setLabel(formConfigQuery.getDescription());
            }
            if (Objects.equals(configType, FORM)) {
                editor.setFormBatProp();
                editor.setRequired(Objects.equals(formConfigQuery.getIsRequired(), (byte) 1));
            } else {
                editor.setSize("sm");
            }
            return editor;
        }

    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class ServiceBody extends Component {
        private Api schemaApi;
        private Api api;
        @JsonIgnore
        private String domain;
        @JsonIgnore
        private String contextPath;

        public ServiceBody(String domain, String contextPath) {
            super("service");
            this.domain = domain;
            this.contextPath = contextPath;
        }

        public ServiceBody() {
            super("service");
        }


        public void buildLoadSchema(Long formId) {
            schemaApi = Api.builder().method(HTTP_GET).url(domain + contextPath + "/form/load/" + formId).build();
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class Flex extends Component {
        private String justify;
        private List<? extends Component> items;

        public Flex() {
            super("flex");
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class Hidden extends Component {
        public static final String TYPE = "hidden";
        private Object value;

        public Hidden() {
            super(TYPE);
        }

        public static Hidden buildHidden(String name, Object value) {
            Hidden hidden = new Hidden();
            hidden.setName(name);
            if (value != null) {
                hidden.setValue(value);
            }
            return hidden;
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class SearchBox extends Component {
        private boolean canAccessSuperData = false;

        public SearchBox() {
            super("search-box");
        }
    }

    public static class Operation extends Component {
        public Operation() {
            super("operation");
        }

        public static Column build(List<FormConfigQueryVo> formConfigQueryList, FormDefDetail formDefDetail,
                                   String reloadComponent, boolean crud) {
            List<Component> buttons = buildListExtComponents(formDefDetail, formConfigQueryList, reloadComponent, crud, Set.of(OPERATION_LINE));
            if (buttons.isEmpty()) {
                return null;
            }
            Column.CommonColumn column = new Column.CommonColumn();
            column.setType("operation");
            column.setLabel("#i18n{operation}");
            column.setFixed("right");
            column.setButtons(buttons);
            return column;
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class Drawer extends Component {
        private boolean closeOnOutside = false;
        private boolean closeOnEsc = true;
        private boolean resizable = true;
        private String size;
        private String position;
        private Map<String, Object> data;

        public Drawer() {
            super("drawer");
        }

        public static String getTypeName() {
            return "drawer";
        }
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class Select extends FormBase {
        private Boolean multiple;
        private List<com.hwtx.form.domain.def.Select.Option> options;
        private Api source;
        private Boolean searchable;
        private Boolean checkAll;

        public Select(String type) {
            super(type);
        }

        public Select() {
            super("select");
        }
    }
}

